package org.terukusu.dropboxdownloader;

import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URLDecoder;
import java.text.DateFormatSymbols;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import org.terukusu.util.LRULinkedHashMap;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.net.Uri;
import android.os.AsyncTask.Status;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import com.dropbox.client.DropboxAPI;
import com.dropbox.client.DropboxAPI.Entry;

/**
 * DropBoxのフォルダをブラウズするアクティビティ
 *
 * @author Teruhiko Kusunoki&lt;<a href="teru.kusu@gmail.com">teru.kusu@gmail.com</a>&gt;
 *
 */
public class BrowseActivity extends Activity implements PrefConstans {
    /**
     * TAG for {@link android.util.Log}
     */
    private static final String TAG = BrowseActivity.class.getSimpleName();

    /** DropBoxファイルリストのキャッシュ数 */
    private static final int ENTRY_CACHE_CAPACITY = 20;

    /** 進捗ダイアログのダイアログID */
    private static final int DIALOG_PROGRESS  = 1;

    /** 進捗ダイアログのダイアログID */
    private static final int DIALOG_DL_PREPARE  = 2;

    /** DL進捗ダイアログのダイアログID */
    private static final int DIALOG_DL_PROGRESS  = 3;

    /** 再読込メニュー */
    private static final int MENU_RELOAD = 1;

    /** ログオフメニュー */
    private static final int MENU_LOGOFF = 2;

    /** ログインアクティビティのID */
    private static final int ACTIVITY_LOGIN = 1;

    /** DLフォルダ選択アクティビティ */
    private static final int ACTIVITY_FOLDER_PICK = 2;

    private final DropboxManager dropboxManager = DropboxManager.getInstance();

    /** ダウンロード元のDropBoxパス */
    private String dlFromPath;

    /** ダウンロード先のローカルフォルダパス */
    private String dlToPath;

    /** 同期コピーを行うかどうか */
    private boolean isSyncDl = false;

    /** 表示中のドロップボックスのパス */
    private String dbPath = "/";

    /** 表示中のドロップボックスのパスのメタデータ */
    private DropboxAPI.Entry entry;

    /** 次回バックキーで終了するかどうか */
    private boolean isTobeExit = false;

    /** 既定のダウンロード先を使用するか */
    private boolean isUseDefaultLocation = false;

    /** リストアダプタ */
    private DropBoxListAdapter listAdapter;

    /** メタデータ取得用非同期タスクからを通知を受け取るためのリスナー */
    private final BrowseDropBoxTaskListener browseDropBoxTaskListener = new BrowseDropBoxTaskListener();

    /** DropBoxのメタデータを取得するための非同期タスク */
    private BrowseDropBoxTask browseDropBoxTask;

    private final DownloadTaskListener downloadTaskListener = new DownloadTaskListener();

    /** DropBoxのファイルを取得するための非同期タスク */
    private DownloadTask downloadTask;

    /** DropBoxのファイルを取得するための非同期タスク */
    private DownloadPrepareTaskListener downloadPrepareTaskListener = new DownloadPrepareTaskListener();

    /** DropBoxのファイルを取得するための非同期タスク */
    private DownloadPrepareTask downloadPrepareTask;

    /** DropBoxのパスとエントリのマップ */
    private Map<String, DropboxAPI.Entry> entryCache;

    /** プログレスダイアログ */
    private ProgressDialog progressDialog;

    /* (non-Javadoc)
     * @see android.app.Activity#onDestroy()
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();
        getEntryCache().clear();
    }

    /* (non-Javadoc)
     * @see android.app.Activity#onCreate(android.os.Bundle)
     */
    @Override
    @SuppressWarnings("unchecked")
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.v(TAG, "[" + Thread.currentThread().getName() + ":" + Thread.currentThread().getId()
                + "] onCreate() called: hashCode=" + hashCode());

        setContentView(R.layout.browse);

        Log.v(TAG, "dropBoxManager=" + getDropboxManager());

        if (getLastNonConfigurationInstance() != null) {
            // 画面回転等による再構築
            Map<String, Object> instances = (Map<String, Object>) getLastNonConfigurationInstance();
            setDbPath((String) instances.get("dbPath"));
            setEntry((Entry) instances.get("entry"));
            setTobeExit((Boolean)instances.get("isTobeExit"));
            setListAdapter((DropBoxListAdapter) instances.get("listAdapter"));
            setBrowseDropBoxTask((BrowseDropBoxTask) instances.get("browseDropBoxTask"));
            setEntryCache((Map<String, Entry>) instances.get("entryCache"));
            setDlFromPath((String) instances.get("dlFromPath"));
            setDlToPath((String) instances.get("dlToPath"));
            setSyncDl((Boolean) instances.get("isSyncDl"));
            setUseDefaultLocation((Boolean) instances.get("useDefaultLocation"));
            setDownloadPrepareTask((DownloadPrepareTask) instances.get("downloadPrepareTask"));
            setDownloadTask((DownloadTask) instances.get("downloadTask"));

            if (getBrowseDropBoxTask() != null) {
                getBrowseDropBoxTask().setAsyncTaskListener(getBrowseDropBoxTaskListener());
            }
            if (getDownloadPrepareTask() != null) {
                getDownloadPrepareTask().setAsyncTaskListener(getDownloadPrepareTaskListener());
            }
            if (getDownloadTask() != null) {
                getDownloadTask().setAsyncTaskListener(getDownloadTaskListener());
            }
        } else {
            entryCache = new LRULinkedHashMap<String, Entry>(ENTRY_CACHE_CAPACITY);
            setListAdapter(new DropBoxListAdapter(this, R.layout.file_list_item));
        }


        ListView fileList = (ListView) findViewById(R.id.fileList);
        fileList.setAdapter(getListAdapter());

        // タップ
        fileList.setOnItemClickListener(new OnItemClickListener() {
            /* (non-Javadoc)
             * @see android.widget.AdapterView.OnItemClickListener#onItemClick(android.widget.AdapterView, android.view.View, int, long)
             */
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                final DropboxAPI.Entry entry = getEntry().contents.get(position);
                Log.v(TAG, "item selected: path=" + entry.path);

                if (entry.is_dir) {
                    browseDropBox(entry.path, false);
                } else {
                    // ファイルDL窓出す
                    AlertDialog.Builder builder = getDownloadDialogBuilder(entry);
                    builder.show();
                }
            }
        });

        // 長押し
        fileList.setOnItemLongClickListener(new OnItemLongClickListener() {

            /* (non-Javadoc)
             * @see android.widget.AdapterView.OnItemLongClickListener#onItemLongClick(android.widget.AdapterView, android.view.View, int, long)
             */
            @Override
            public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
                DropboxAPI.Entry entry = (DropboxAPI.Entry) parent.getItemAtPosition(position);
                // ファイルDL窓出す
                AlertDialog.Builder builder = getDownloadDialogBuilder(entry);
                builder.show();
                return true;
            }
        });

        if (getLastNonConfigurationInstance() == null) {
            browseDropBox(getDbPath(), false);
        }
    }

    /**
     * ダウンロードダイアログ用のビルダーを取得します。
     * @param entry ダウンロード対象
     * @return ダウンロードダイアログ用のビルだ。
     */
    public AlertDialog.Builder getDownloadDialogBuilder(final DropboxAPI.Entry entry) {
        LayoutInflater li = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
        View view = (View) li.inflate(R.layout.dl_dialog, null);

        TextView dlTargetText = (TextView) view.findViewById(R.id.dl_dlg_target);
        dlTargetText.setText(entry.fileName());

        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(R.string.browse_alert_download);
        builder.setView(view);
        builder.setNegativeButton(R.string.cancel, null);
        builder.setPositiveButton(R.string.yes, new OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                switch (which) {
                case DialogInterface.BUTTON_POSITIVE:
                    // 変更の有ったものだけDL?
                    CheckBox syncCheck = (CheckBox) ((AlertDialog) dialog).findViewById(R.id.dl_dlg_sync_check);
                    CheckBox defaultLocCheck = (CheckBox) ((AlertDialog) dialog).findViewById(R.id.dl_dlg_default_location);

                    // DL条件をフィールドに保存
                    setUseDefaultLocation(defaultLocCheck.isChecked());
                    setSyncDl(syncCheck.isChecked());
                    setDlFromPath(entry.path);
                    setDlToPath(null);


                    // デフォルトの保存場所が利用可能か
                    boolean isDefaultLocationAvailable = false;

                    SharedPreferences pref = getSharedPreferences(PREF, MODE_PRIVATE);
                    String defaultLocation = pref.getString(PREF_DEFAULT_SAVE_DIRECTORY, null);
                    Log.v(TAG, "defaultLocation: " + defaultLocation);

                    if (defaultLocation != null && defaultLocation.length() > 0) {
                        File file = new File(defaultLocation);
                        isDefaultLocationAvailable = (file.exists() && file.isDirectory());

                        // DL先パスを設定
                        setDlToPath(defaultLocation);
                    }
                    Log.v(TAG, "isDefaultLocationAvailable: " + isDefaultLocationAvailable);

                    if (!isUseDefaultLocation() || !isDefaultLocationAvailable) {
                        // ダウンロード先取得アクティビティ起動
                        Intent intent = new Intent("org.openintents.action.PICK_DIRECTORY");
                        intent.setData(Uri.parse("file:///sdcard"));
                        intent.putExtra("org.openintents.extra.TITLE", getText(R.string.browse_pick_location_title));
                        intent.putExtra("org.openintents.extra.BUTTON_TEXT", getText(R.string.browse_pick_location_text));
                        try {
                            startActivityForResult(intent, ACTIVITY_FOLDER_PICK);
                        } catch (ActivityNotFoundException e) {
                            // No compatible file manager was found.
                            Toast.makeText(BrowseActivity.this, R.string.error_no_oi_filemanager,
                                    Toast.LENGTH_LONG).show();
                        }
                    } else {
                        download(getDlFromPath(), getDlToPath(), isSyncDl());
                    }
                    break;
                }
            }
        });
        return builder;
    }

    /**
     *
     * @param from
     * @param to
     * @param isSync
     */
    public void download(String from, String to, boolean isSync) {
        DownloadPrepareTask task = new DownloadPrepareTask();
        task.setAsyncTaskListener(getDownloadPrepareTaskListener());
        setDownloadPrepareTask(task);
        task.execute(from, to, isSync);
    }

    private class DownloadTaskListener extends AsyncTaskAdapter<Integer, Object> {

        /* (non-Javadoc)
         * @see org.terukusu.dropboxdownloader.AsyncTaskAdapter#onPostExecute(java.lang.Object)
         */
        @Override
        public void onPostExecute(Object result) {
            if (result == null) {
                Toast.makeText(BrowseActivity.this, getText(R.string.browse_dl_download_failed), Toast.LENGTH_LONG).show();
            } else {
                Toast.makeText(BrowseActivity.this, getDlToPath() + getText(R.string.browse_dl_download_success), Toast.LENGTH_LONG).show();
            }
            removeDialog(DIALOG_DL_PROGRESS);
            setDownloadPrepareTask(null);
            setDownloadTask(null);
        }

        /* (non-Javadoc)
         * @see org.terukusu.dropboxdownloader.AsyncTaskAdapter#onPreExecute()
         */
        @Override
        public void onPreExecute() {
            showDialog(DIALOG_DL_PROGRESS);
        }

        /* (non-Javadoc)
         * @see org.terukusu.dropboxdownloader.AsyncTaskAdapter#onProgressUpdate(Progress[])
         */
        @Override
        public void onProgressUpdate(Integer... values) {
            if (getProgressDialog() != null) {
                getProgressDialog().setProgress(values[0]);
            }
        }
    }

    private class DownloadTask extends AsyncTask<Object, Integer, Object> {
        private final SimpleDateFormat MODIFIED_FORMAT = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z", new DateFormatSymbols(Locale.US));
        private int maxDownloadCount;
        private int currentCount = 0;
        private String toPath;
        private boolean isSync;

        /** DL対象 */
        private DropboxAPI.Entry entry;

        /* (non-Javadoc)
         * @see android.os.AsyncTask#doInBackground(Params[])
         */
        @Override
        protected Object doInBackground(Object... params) {
            try {
                download(getEntry(), null, getToPath(), isSync());
                return true;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }

        /**
         * entryの構造を保ったままDLします。
         *
         * @param origin DLの起点となるDropBox上のパス。entry が
         * @param entry
         */
        public void download(DropboxAPI.Entry entry, String origin, String toPath, boolean isSync) throws DropBoxException {
            File toPathFile = new File(toPath);
            if (toPathFile.exists()) {
                if (!toPathFile.isDirectory()) {
                    throw new IllegalArgumentException("specified path is not a directory: " + toPath);
                }
            } else {
                if(!toPathFile.mkdirs()) {
                    throw new IllegalArgumentException("cannot create directory: " + toPath);
                }
            }

            if (origin == null) {
                origin = entry.parentPath();
            }

            if (!entry.path.toUpperCase().startsWith(origin.toUpperCase())) {
                throw new IllegalArgumentException("entry.path not starts with origin: entry.path = " + entry.path + ", origin = " + origin);
            }

            // DL基準からの相対パス
            String relPath = entry.path.substring(origin.length());
            File localFile = new File(toPath, relPath);
            File parent = localFile.getParentFile();

            if (!parent.exists() && !localFile.getParentFile().mkdirs()) {
                throw new IllegalArgumentException("can't create directory: " + localFile.getParent());
            }

            Date dbDate = null;
            try {
                dbDate = MODIFIED_FORMAT.parse(entry.modified);
            } catch (ParseException e) {
                throw new DropBoxException("entry.modfied invalid format: " + entry.modified, e);
            }

            if (!entry.is_dir) {
                // ただのファイル
                try {
                    Log.v(TAG, "lastModified: dbPath = " + dbDate.getTime() + ", localFile = " + localFile.lastModified());
                    if (!localFile.exists() || !isSync() || localFile.lastModified() != dbDate.getTime()) {
                        Log.v(TAG, "download file: from = " + entry.path + ", to = " + localFile.getAbsolutePath());
                        getDropboxManager().getFile(entry.path, localFile);
                        localFile.setLastModified(dbDate.getTime());
                    } else {
                        Log.v(TAG, "skip downlaod: dbPath = " + entry.path);
                    }

                    currentCount++;
                    publishProgress(currentCount);
                } catch (IOException e) {
                    new DropBoxException("download failed: entry.path = " + entry.path, e);
                }
            } else {
                // ディレクトリ
                if (localFile.mkdirs()) {
                    Log.v(TAG, "directory created: path = " + localFile.getAbsolutePath());
                }
                if (entry.contents != null) {
                    for (DropboxAPI.Entry child: entry.contents) {
                        download(child, origin, toPath, isSync);
                    }
                }
            }
        }

        /**
         * toPath を取得します。
         * @return toPath
         */
        public String getToPath() {
            return toPath;
        }

        /**
         * toPath を設定します。
         * @param toPath セットする toPath
         */
        public void setToPath(String toPath) {
            this.toPath = toPath;
        }

        /**
         * isSync を取得します。
         * @return isSync
         */
        public boolean isSync() {
            return isSync;
        }

        /**
         * isSync を設定します。
         * @param isSync セットする isSync
         */
        public void setSync(boolean isSync) {
            this.isSync = isSync;
        }

        /**
         * Entry に含まれるファイル数を取得する
         *
         * @return
         */
        public int countFiles(Entry entry) {
            int i = 0;
            if (entry.is_dir) {
                if (entry.contents != null) {
                    for (Entry child: entry.contents) {
                        i += countFiles(child);
                    }
                }
            } else {
                i++;
            }
            return i;
        }

        /**
         * entry を取得します。
         * @return entry
         */
        public DropboxAPI.Entry getEntry() {
            return entry;
        }

        /**
         * entry を設定します。
         * @param entry セットする entry
         */
        public void setEntry(DropboxAPI.Entry entry) {
            int count = countFiles(entry);
            setMaxDownloadCount(count);
            dumpEntry(entry);
            Log.v(TAG, "maxDownloadCount = " + count);
            this.entry = entry;
        }

        /**
         * maxDownloadCount を取得します。
         * @return maxDownloadCount
         */
        public synchronized int getMaxDownloadCount() {
            return maxDownloadCount;
        }

        /**
         * maxDownloadCount を設定します。
         * @param maxDownloadCount セットする maxDownloadCount
         */
        public synchronized void setMaxDownloadCount(int maxDownloadCount) {
            this.maxDownloadCount = maxDownloadCount;
        }
    }

    private class DownloadPrepareTaskListener extends AsyncTaskAdapter<Integer, Object> {

        /* (non-Javadoc)
         * @see org.terukusu.dropboxdownloader.AsyncTaskAdapter#onPostExecute(java.lang.Object)
         */
        @Override
        public void onPostExecute(Object result) {
            Log.v(TAG, "[" + Thread.currentThread().getName() + ":" + Thread.currentThread().getId()
                    + "] onPostExecute() called: hashCode=" + hashCode() + ", result = " + result);

            if (result == null) {
                // 失敗
                Toast.makeText(BrowseActivity.this, getText(R.string.browse_dl_download_failed), Toast.LENGTH_LONG).show();
            } else {
                DownloadTask task = new DownloadTask();
                task.setAsyncTaskListener(getDownloadTaskListener());
                task.setEntry((DropboxAPI.Entry) result);
                task.setToPath(getDlToPath());
                task.setSync(isSyncDl());
                setDownloadTask(task);
                task.execute();
            }

            removeDialog(DIALOG_DL_PREPARE);
        }

        /* (non-Javadoc)
         * @see org.terukusu.dropboxdownloader.AsyncTaskAdapter#onPreExecute()
         */
        @Override
        public void onPreExecute() {
            showDialog(DIALOG_DL_PREPARE);
        }
    }

    /**
     * DropBox上のDL対象の構造を取得するタスク。
     *
     * @author Teruhiko Kusunoki&lt;<a href="teru.kusu@gmail.com">teru.kusu@gmail.com</a>&gt;
     *
     */
   private class DownloadPrepareTask extends AsyncTask<Object, Integer, Object> {
       /* (non-Javadoc)
        * @see android.os.AsyncTask#doInBackground(Params[])
        */
       @Override
       protected Object doInBackground(Object... args) {
           // args = fromPath
           try {
               DropboxAPI.Entry result = buildDbMetaTree((String) args[0]);
               dumpEntry(result);
               return result;
           } catch (Exception e) {
               e.printStackTrace();
           }

           return null;
       }
   }

    // デバッグ用
    public void dumpEntry(Entry entry) {
        Log.v(TAG, "dbPath = " + entry.path);
        if (entry.is_dir && entry.contents != null) {
            for (Entry child : entry.contents) {
                dumpEntry(child);
            }
        }
    }


    /**
     * 指定されたのDropBoxパス以下のメタデータを再帰的に取得します。
     * (DropBox API のマニュアルには再帰するなって書いてあったけど・・)
     *
     * @param dbPath DropBox上のパス
     * @return 指定されたのDropBoxパス以下のメタデータ
     */
    public DropboxAPI.Entry buildDbMetaTree(String dbPath) throws DropBoxFileNotFoundException, DropBoxException {
        DropboxAPI.Entry entry = getDropboxManager().getMetaData(dbPath);
        buildDbMetaTree(entry);

        return entry;
    }

    /**
     * 再帰用
     *
     * @param dbPath
     * @return
     * @throws DropBoxFileNotFoundException
     * @throws DropBoxException
     */
    protected void buildDbMetaTree(DropboxAPI.Entry entry) throws DropBoxFileNotFoundException, DropBoxException {
        if (entry == null) {
            throw new DropBoxException("cannot retrieve metadata: dbPath=" + dbPath);
        }

        Log.v(TAG, "[" + Thread.currentThread().getName() + ":" + Thread.currentThread().getId()
                + "] buildDbMetaTree() called: hashCode=" + hashCode() + ", dbPath = " + entry.path);

        if (entry.httpCode == HttpURLConnection.HTTP_NOT_FOUND) {
            throw new DropBoxFileNotFoundException("DropBox file not found: " + dbPath, dbPath);
        }

        if (entry.isError()) {
            throw new DropBoxException("cannot retrieve metadata: httpCode = " + entry.httpCode + ", httpResponse = " + entry.httpReason);
        }

        if (!entry.is_dir || entry.contents == null) {
            return;
        }

        for (DropboxAPI.Entry childEntry : entry.contents) {
            if (childEntry.is_dir) {
                DropboxAPI.Entry tmpEntry = getDropboxManager().getMetaData(childEntry.path);
                childEntry.contents = tmpEntry.contents;
                buildDbMetaTree(childEntry);
            }
        }
    }

    /* (non-Javadoc)
     * @see android.app.Activity#onRetainNonConfigurationInstance()
     */
    @Override
    public Object onRetainNonConfigurationInstance() {
        Log.v(TAG, "[" + Thread.currentThread().getName() + ":" + Thread.currentThread().getId()
                + "] onRetainNonConfigurationInstance() called: hashCode=" + hashCode());

        Map<String, Object> instances = new HashMap<String, Object>();
        instances.put("dbPath", getDbPath());
        instances.put("entry", getEntry());
        instances.put("isTobeExit", isTobeExit());
        instances.put("listAdapter", getListAdapter());
        instances.put("browseDropBoxTask", getBrowseDropBoxTask());
        instances.put("entryCache", getEntryCache());
        instances.put("dlFromPath", getDlFromPath());
        instances.put("dlToPath", getDlToPath());
        instances.put("isSyncDl", isSyncDl());
        instances.put("useDefaultLocation", isUseDefaultLocation());
        instances.put("downloadTask", getDownloadTask());
        instances.put("downloadPrepareTask", getDownloadPrepareTask());

        if (getBrowseDropBoxTask() != null) {
            getBrowseDropBoxTask().setAsyncTaskListener(null);
        }
        if (getDownloadPrepareTask() != null) {
            getDownloadPrepareTask().setAsyncTaskListener(null);
        }
        if (getDownloadTask() != null) {
            getDownloadTask().setAsyncTaskListener(null);
        }

        return instances;
    }

    /* (non-Javadoc)
     * @see android.app.Activity#onResume()
     */
    @Override
    protected void onResume() {
        super.onResume();
        Log.v(TAG, "[" + Thread.currentThread().getName() + ":" + Thread.currentThread().getId()
                + "] onResume() called: hashCode=" + hashCode());

        // backボタン終了フラグをリセット
        setTobeExit(false);
        setTitle(getDbPath());
    }

    /**
     * 指定したDropBox上のパスをブラウズします。
     *
     * @param path DropBox上のパス
     */
    protected void browseDropBox(String path, boolean forceRefresh) {
        Log.v(TAG, "[" + Thread.currentThread().getName() + ":" + Thread.currentThread().getId()
                + "] browseDropBox() called: hashCode=" + hashCode() + ", path = " + path);

        if (!forceRefresh) {
            DropboxAPI.Entry entry = getEntryCache().get(path);
            if (entry != null) {
                // キャッシュを使って更新
                setEntry(entry);
                if ("".equals(entry.path)) {
                    setDbPath("/");
                } else {
                    setDbPath(entry.path);
                }

                updateView();

                return;
            }
        }

        browseDropBoxTask = new BrowseDropBoxTask(path);
        browseDropBoxTask.setAsyncTaskListener(getBrowseDropBoxTaskListener());

        if (!getDropboxManager().isAuthenticated()) {
            startActivityForResult(new Intent(this, LoginActivity.class), ACTIVITY_LOGIN);
            return;
        }

        browseDropBoxTask.execute();
    }


    /**
     * ビューを現在の設定で更新します
     */
    protected void updateView() {
        if (getEntry() == null || getEntry().contents == null) {
            Log.i(TAG, "updateView(): entry or contents is null.");
            return;
        }

        // backボタン終了フラグリセット
        setTobeExit(false);

        setTitle(getDbPath());

        DropBoxListAdapter listAdapter = getListAdapter();
        listAdapter.clear();
        if (entry.contents != null) {
            for (DropboxAPI.Entry child : entry.contents) {
                listAdapter.add(child);
            }
        }

        ListView fileList = (ListView) findViewById(R.id.fileList);
        fileList.setSelectionAfterHeaderView();
        fileList.clearFocus();

        listAdapter.notifyDataSetChanged();
    }

    /* (non-Javadoc)
     * @see android.app.Activity#onActivityResult(int, int, android.content.Intent)
     */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
        case ACTIVITY_LOGIN:
            if (resultCode == RESULT_OK) {
                if (getBrowseDropBoxTask() != null && getBrowseDropBoxTask().getStatus() == Status.PENDING) {
                    getBrowseDropBoxTask().execute();
                }
            }
            break;
        case ACTIVITY_FOLDER_PICK:
            if (resultCode == RESULT_OK && data != null) {
                // obtain the filename
                String tmpFilename = data.getDataString();
                if (tmpFilename != null) {
                    // Get rid of URI prefix:
                    if (tmpFilename.startsWith("file://")) {
                        tmpFilename = tmpFilename.substring(7);
                    }

                    try {
                        tmpFilename = URLDecoder.decode(tmpFilename, System.getProperty("file.encoding"));
                    } catch (Exception ignored) {
                        Log.v(TAG, "unsupported encoding", ignored);
                    }

                    final String filename = tmpFilename;
                    setDlToPath(filename);

                    // デフォルトの保存フォルダにするかの確認

                    // アラートダイアログのリスナ
                    OnClickListener listener =  new OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            switch(which) {
                            case AlertDialog.BUTTON_POSITIVE:
                                SharedPreferences pref = getSharedPreferences(PREF, MODE_PRIVATE);
                                Editor editor = pref.edit();
                                editor.putString(PREF_DEFAULT_SAVE_DIRECTORY, filename);
                                editor.commit();

                                break;
                            }

                            // yes でも no でも DL開始
                            download(getDlFromPath(), getDlToPath(), isSyncDl());
                        }
                    };

                    // アラートダイアログ表示
                    AlertDialog.Builder builder = new AlertDialog.Builder(this);
                    builder.setTitle(R.string.browse_alert_use_as_default);
                    builder.setNegativeButton(R.string.no, listener);
                    builder.setPositiveButton(R.string.yes, listener);
                    builder.show();
                }
            }
            break;
        }
    }

    /* (non-Javadoc)
     * @see android.app.Activity#onKeyDown(int, android.view.KeyEvent)
     */
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        switch (keyCode) {
        case KeyEvent.KEYCODE_BACK:
            if ("/".equals(getDbPath())) {
                if(isTobeExit()) {
                    return super.onKeyDown(keyCode, event);
                } else {
                    setTobeExit(true);
                    Toast.makeText(this, R.string.browse_tobe_exit, Toast.LENGTH_SHORT).show();
                    return true;
                }
            } else {
                String targetPath = getDbPath().substring(0, getDbPath().lastIndexOf("/"));
                if (targetPath.equals("")) {
                    targetPath = "/";
                }

                browseDropBox(targetPath, false);
            }
            return true;
        default:
            return super.onKeyDown(keyCode, event);
        }
    }



    /* (non-Javadoc)
     * @see android.app.Activity#onCreateDialog(int)
     */
    @Override
    protected Dialog onCreateDialog(int id) {
        Log.v(TAG, "[" + Thread.currentThread().getName() + ":" + Thread.currentThread().getId()
                + "] onCreateDialog() called: hashCode=" + hashCode());

        Log.v(TAG, "craeting dialog: id=" + id);
        switch (id) {
        case DIALOG_PROGRESS:
            ProgressDialog d = new ProgressDialog(BrowseActivity.this);
            d.setProgressStyle(ProgressDialog.STYLE_SPINNER);
            d.setCancelable(false);
            d.setMessage(getText(R.string.browse_get_data));
            Log.v(TAG, "dialog created: hashCode=" + d.hashCode());
            return d;
        case DIALOG_DL_PREPARE:
            ProgressDialog d2 = new ProgressDialog(BrowseActivity.this);
            d2.setProgressStyle(ProgressDialog.STYLE_SPINNER);
            d2.setIndeterminate(false);
            d2.setCancelable(true);
            d2.setMessage(getText(R.string.browse_dl_prepare));
            Log.v(TAG, "dialog created: hashCode=" + d2.hashCode());
            return d2;
        case DIALOG_DL_PROGRESS:
            ProgressDialog d3 = new ProgressDialog(BrowseActivity.this);
            d3.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
            d3.setIndeterminate(false);
            d3.setCancelable(false);
            d3.setMessage(getText(R.string.browse_dl_downloading));
            if (getDownloadTask() != null) {
                d3.setMax(getDownloadTask().getMaxDownloadCount());
            }
            setProgressDialog(d3);
            Log.v(TAG, "dialog created: hashCode=" + d3.hashCode());
            return d3;
        default:
            return null;
        }
    }

    /* (non-Javadoc)
     * @see android.app.Activity#onPrepareDialog(int, android.app.Dialog)
     */
    @Override
    protected void onPrepareDialog(int id, Dialog dialog) {
        super.onPrepareDialog(id, dialog);
        Log.v(TAG, "[" + Thread.currentThread().getName() + ":" + Thread.currentThread().getId()
                + "] onPrepareDialog() called: hashCode=" + hashCode() + ", Dialog id=" + id + ", Dialog hashCode=" + dialog.hashCode());

        switch (id) {
        case DIALOG_PROGRESS:
            ((ProgressDialog) dialog).setProgress(0);
            break;
        }
    }



    /* (non-Javadoc)
     * @see android.app.Activity#onCreateOptionsMenu(android.view.Menu)
     */
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        menu.add(Menu.NONE, MENU_RELOAD, Menu.NONE, R.string.browse_menu_reload);
        menu.add(Menu.NONE, MENU_LOGOFF, Menu.NONE, R.string.browse_menu_logoff);
        return super.onCreateOptionsMenu(menu);
    }

    /* (non-Javadoc)
     * @see android.app.Activity#onOptionsItemSelected(android.view.MenuItem)
     */
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case MENU_RELOAD:
            browseDropBox(getDbPath(), true);
            return true;
        case MENU_LOGOFF:
            getDropboxManager().deauthenticate();

            SharedPreferences pref = (SharedPreferences) getSharedPreferences(PREF, MODE_PRIVATE);
            Editor editor = pref.edit();
            editor.remove(PrefConstans.PREF_ACCESS_TOKEN_KEY);
            editor.remove(PrefConstans.PREF_ACCESS_TOKEN_SECRET);
            editor.commit();
            startActivityForResult(new Intent(this, LoginActivity.class), ACTIVITY_LOGIN);
            return true;
        default:
            return super.onOptionsItemSelected(item);
        }
    }

    /**
     * dropboxManager を取得します。
     * @return dropboxManager
     */
    public DropboxManager getDropboxManager() {
        return dropboxManager;
    }

    /**
     * dbPath を取得します。
     * @return dbPath
     */
    public String getDbPath() {
        return dbPath;
    }

    /**
     * dbPath を設定します。
     * @param dbPath セットする dbPath
     */
    public void setDbPath(String dbPath) {
        this.dbPath = dbPath;
    }

    /**
     * entry を取得します。
     * @return entry
     */
    public DropboxAPI.Entry getEntry() {
        return entry;
    }

    /**
     * entry を設定します。
     * @param entry セットする entry
     */
    public void setEntry(DropboxAPI.Entry entry) {
        this.entry = entry;
    }

    /**
     * isTobeExit を取得します。
     * @return isTobeExit
     */
    public boolean isTobeExit() {
        return isTobeExit;
    }

    /**
     * isTobeExit を設定します。
     * @param isTobeExit セットする isTobeExit
     */
    public void setTobeExit(boolean isTobeExit) {
        this.isTobeExit = isTobeExit;
    }

    /**
     * listAdapter を取得します。
     * @return listAdapter
     */
    public DropBoxListAdapter getListAdapter() {
        return listAdapter;
    }

    /**
     * listAdapter を設定します。
     * @param listAdapter セットする listAdapter
     */
    public void setListAdapter(DropBoxListAdapter listAdapter) {
        this.listAdapter = listAdapter;
    }

    /**
     * browseDropBoxTask を取得します。
     * @return browseDropBoxTask
     */
    public BrowseDropBoxTask getBrowseDropBoxTask() {
        return browseDropBoxTask;
    }

    /**
     * browseDropBoxTask を設定します。
     * @param browseDropBoxTask セットする browseDropBoxTask
     */
    public void setBrowseDropBoxTask(BrowseDropBoxTask browseDropBoxTask) {
        this.browseDropBoxTask = browseDropBoxTask;
    }

    /**
     * browseDropBoxTaskListener を取得します。
     * @return browseDropBoxTaskListener
     */
    public BrowseDropBoxTaskListener getBrowseDropBoxTaskListener() {
        return browseDropBoxTaskListener;
    }


    /**
     * entryCache を取得します。
     * @return entryCache
     */
    protected Map<String, DropboxAPI.Entry> getEntryCache() {
        return entryCache;
    }

    /**
     * entryCache を設定します。
     * @param entryCache セットする entryCache
     */
    protected void setEntryCache(Map<String, DropboxAPI.Entry> entryCache) {
        this.entryCache = entryCache;
    }

    /**
     * dlFromPath を取得します。
     * @return dlFromPath
     */
    public String getDlFromPath() {
        return dlFromPath;
    }

    /**
     * dlFromPath を設定します。
     * @param dlFromPath セットする dlFromPath
     */
    public void setDlFromPath(String dlFromPath) {
        this.dlFromPath = dlFromPath;
    }

    /**
     * dlToPath を取得します。
     * @return dlToPath
     */
    public String getDlToPath() {
        return dlToPath;
    }

    /**
     * dlToPath を設定します。
     * @param dlToPath セットする dlToPath
     */
    public void setDlToPath(String dlToPath) {
        this.dlToPath = dlToPath;
    }

    /**
     * isSyncDl を取得します。
     * @return isSyncDl
     */
    public boolean isSyncDl() {
        return isSyncDl;
    }

    /**
     * isSyncDl を設定します。
     * @param isSyncDl セットする isSyncDl
     */
    public void setSyncDl(boolean isSyncDl) {
        this.isSyncDl = isSyncDl;
    }

    /**
     * isUseDefaultLocation を取得します。
     * @return isUseDefaultLocation
     */
    public boolean isUseDefaultLocation() {
        return isUseDefaultLocation;
    }

    /**
     * isUseDefaultLocation を設定します。
     * @param isUseDefaultLocation セットする isUseDefaultLocation
     */
    public void setUseDefaultLocation(boolean isUseDefaultLocation) {
        this.isUseDefaultLocation = isUseDefaultLocation;
    }



    /**
     * progressDialog を取得します。
     * @return progressDialog
     */
    public ProgressDialog getProgressDialog() {
        return progressDialog;
    }

    /**
     * progressDialog を設定します。
     * @param progressDialog セットする progressDialog
     */
    public void setProgressDialog(ProgressDialog progressDialog) {
        this.progressDialog = progressDialog;
    }

    /**
     * downloadTaskListener を取得します。
     * @return downloadTaskListener
     */
    public DownloadTaskListener getDownloadTaskListener() {
        return downloadTaskListener;
    }

    /**
     * downloadTask を取得します。
     * @return downloadTask
     */
    public DownloadTask getDownloadTask() {
        return downloadTask;
    }

    /**
     * downloadPrepareTaskListener を取得します。
     * @return downloadPrepareTaskListener
     */
    public DownloadPrepareTaskListener getDownloadPrepareTaskListener() {
        return downloadPrepareTaskListener;
    }

    /**
     * downloadPrepareTaskListener を設定します。
     * @param downloadPrepareTaskListener セットする downloadPrepareTaskListener
     */
    public void setDownloadPrepareTaskListener(DownloadPrepareTaskListener downloadPrepareTaskListener) {
        this.downloadPrepareTaskListener = downloadPrepareTaskListener;
    }

    /**
     * downloadTask を設定します。
     * @param downloadTask セットする downloadTask
     */
    public void setDownloadTask(DownloadTask downloadTask) {
        this.downloadTask = downloadTask;
    }

    /**
     * downloadPrepareTask を取得します。
     * @return downloadPrepareTask
     */
    public DownloadPrepareTask getDownloadPrepareTask() {
        return downloadPrepareTask;
    }

    /**
     * downloadPrepareTask を設定します。
     * @param downloadPrepareTask セットする downloadPrepareTask
     */
    public void setDownloadPrepareTask(DownloadPrepareTask downloadPrepareTask) {
        this.downloadPrepareTask = downloadPrepareTask;
    }

    /**
     * DropBoxのファイルリストを表示するためのリストアダプタ
     *
     * @author Teruhiko Kusunoki&lt;<a href="teru.kusu@gmail.com">teru.kusu@gmail.com</a>&gt;
     *
     */
    private class DropBoxListAdapter extends ArrayAdapter<DropboxAPI.Entry> {

        /**
         * 新しいオブジェクトを生成します。
         *
         * @param context
         * @param textViewResourceId
         * @param objects
         */
        public DropBoxListAdapter(Context context, int textViewResourceId, Entry[] objects) {
            super(context, textViewResourceId, objects);
        }

        /**
         * 新しいオブジェクトを生成します。
         *
         * @param context
         * @param resource
         * @param textViewResourceId
         * @param objects
         */
        public DropBoxListAdapter(Context context, int resource, int textViewResourceId, Entry[] objects) {
            super(context, resource, textViewResourceId, objects);
        }

        /**
         * 新しいオブジェクトを生成します。
         *
         * @param context
         * @param resource
         * @param textViewResourceId
         * @param objects
         */
        public DropBoxListAdapter(Context context, int resource, int textViewResourceId, List<Entry> objects) {
            super(context, resource, textViewResourceId, objects);
        }

        /**
         * 新しいオブジェクトを生成します。
         *
         * @param context
         * @param resource
         * @param textViewResourceId
         */
        public DropBoxListAdapter(Context context, int resource, int textViewResourceId) {
            super(context, resource, textViewResourceId);
        }

        /**
         * 新しいオブジェクトを生成します。
         *
         * @param context
         * @param textViewResourceId
         * @param objects
         */
        public DropBoxListAdapter(Context context, int textViewResourceId, List<Entry> objects) {
            super(context, textViewResourceId, objects);
        }

        /**
         * 新しいオブジェクトを生成します。
         *
         * @param context
         * @param textViewResourceId
         */
        public DropBoxListAdapter(Context context, int textViewResourceId) {
            super(context, textViewResourceId);
        }

        /* (non-Javadoc)
         * @see android.widget.ArrayAdapter#getView(int, android.view.View, android.view.ViewGroup)
         */
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View view = convertView;
            if (view == null) {
                LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService(LAYOUT_INFLATER_SERVICE);
                view = layoutInflater.inflate(R.layout.file_list_item, null);
            }

            DropboxAPI.Entry entry = getItem(position);

            TextView nameText = (TextView) view.findViewById(R.id.nameText);
            if (entry.is_dir) {
                nameText.setText("[" + entry.fileName() + "]");
            } else {
                nameText.setText(entry.fileName());
            }

            TextView sizeText = (TextView) view.findViewById(R.id.browseSizeText);
            sizeText.setText(entry.size);

            return view;
        }
    }

    private class BrowseDropBoxTask extends AsyncTask<Object, Integer, DropboxAPI.Entry> {

        private final String targetPath;

        /**
         * 新しいオブジェクトを生成します。
         *
         * @param targetPath
         */
        public BrowseDropBoxTask(String targetPath) {
            super();
            this.targetPath = targetPath;
        }

        /* (non-Javadoc)
         * @see android.os.AsyncTask#doInBackground(Params[])
         */
        @Override
        protected Entry doInBackground(Object... params) {
            Entry entry = getDropboxManager().getMetaData(getTargetPath());
            return entry;
        }

        /**
         * targetPath を取得します。
         * @return targetPath
         */
        public String getTargetPath() {
            return targetPath;
        }
    }

    /**
     * DropBoxブラウズタスク用のリスナ
     * @author Teruhiko Kusunoki&lt;<a href="teru.kusu@gmail.com">teru.kusu@gmail.com</a>&gt;
     *
     */
    private class BrowseDropBoxTaskListener extends AsyncTaskAdapter<Integer, DropboxAPI.Entry> {

        @Override
        public void onPreExecute() {
            showDialog(DIALOG_PROGRESS);
        }

        @Override
        public void onPostExecute(Entry result) {

            if (result != null) {
                setEntry(result);

                if ("".equals(getEntry().path)) {
                    setDbPath("/");
                } else {
                    setDbPath(getEntry().path);
                }

                getEntryCache().put(getDbPath(), result);
                updateView();
            }

            if (getBrowseDropBoxTask() != null) {
                setBrowseDropBoxTask(null);
            }
            removeDialog(DIALOG_PROGRESS);
        }
    }
}


